-----------Grammar Lab Unit 1----------
-------Basic Sentence Structure--------
A 4am crack                  2017-12-17
---------------------------------------

Name: Grammar Label Unit 1: Basic
  Sentence Structure
Genre: educational
Year: 1986
Credits: design by Michael G. Southwell
  programming by John Fox
Publisher: Michael G. Southwell
Media: single-sided 5.25-inch floppy
OS: DOS 3.3
Previous cracks: none
Similar cracks:
  #600 The Complete Scarsdale Diet

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  disk read error on first pass

Locksmith Fast Disk Backup
  copies everything except track $03;
  disk boots DOS, clears screen, then
  prints "DISK READ ERROR" and halts

EDD 4 bit copy (no sync, no count)
  no errors, but copy exhibits same
  behavior as failed Locksmith FDB copy

Copy ][+ nibble editor
  track $03 appears to contain data,
  but it has no sector headers or
  structure

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 03  START: 1800  LENGTH: 3DFF

1820: FF FF B6 FD B7 FF BA FA   VIEW
1828: BB F7 BD F7 BE BF FB BE
1830: F6 FF FE BF BE BB EA EA
1838: FB AA BA EA EB EF DF BF
1840: FF F7 F6 EF EE AB AB AB  <-1841
1848: FF D5 D5 D5 FF EA EA EA
1850: FF F5 F5 F5 FF FA FA FA
1858: FF FD FD FD FF FE FE FE
1860: FF FF B6 FD B7 FF BA FA

                 --^--

Disk Fixer
  T00 -> DOS 3.3 bootloader / RWTS
  T00-T02 -> full copy of DOS 3.3
  T11 -> DOS 3.3 catalog
  T01,S09 -> startup program is
    "A GRAMMAR LAB"
  Can't find any way to read track $03

Why didn't COPYA work?
  track $03 is intentionally damaged

Why didn't Locksmith FDB / EDD work?
  Probably a nibble check in the
  startup program that reads track 3

Next steps:

  1. Trace the startup program
  2. Disable the nibble check
  3. Declare victory (*)

(*) go to the gym

                   ~

               Chapter 1
   In Which Things Start Off Poorly
        And Go Downhill Rapidly


Booting from my work disk, the non-
working copy ought to have a catalog,
but it has suspiciously vanished.

[S6,D1=non-working copy from Locksmith]
[S5,D1=blank DOS 3.3 disk]

]PR#5
...

]CATALOG,S6,D1


]CATALOG,S6

C1983 DSR^C#254
052 FREE


Turning to my trusty Disk Fixer sector
editor, I immediately go to T11,S00 to
see if there is a simple fix. And there
is...

                 --v--

-------------- DISK EDIT --------------
TRACK $11/SECTOR $00/VOLUME $FE/BYTE$00
---------------------------------------
$00:>04<00 0F 03 00 00 FE 00   D@OC@@~@
        ^^
 track $00? I don't think so

$08: 00 00 00 00 00 00 00 00   @@@@@@@@
$10: 00 00 00 00 00 00 00 00   @@@@@@@@
$18: 00 00 00 00 00 00 00 00   @@@@@@@@
$20: 00 00 00 00 00 00 00 7A   @@@@@@@:
$28: 00 00 00 00 00 00 00 00   @@@@@@@@
$30: 04 FF 00 00 23 10 00 01   D.@@#P@A
$38: 00 00 00 00 00 00 00 00   @@@@@@@@
$40: 00 00 00 00 00 00 00 00   @@@@@@@@
$48: 07 FF 00 00 3F FF 00 00   G.@@?.@@
$50: 1F FF 00 00 00 FF 00 00   _.@@@.@@
$58: 00 00 00 00 00 00 00 00   @@@@@@@@
$60: 00 00 00 00 00 00 00 00   @@@@@@@@
$68: 00 00 00 00 00 00 00 00   @@@@@@@@
$70: 00 00 00 00 00 00 00 00   @@@@@@@@
$78: 00 00 00 00 00 00 00 00   @@@@@@@@
----------------------------------------
BUFFER 0/SLOT 6/DRIVE 1/MASK OFF/NORMAL

----------------------------------------
COMMAND : _

                 --^--

The DOS on this disk apparently
hard-codes the track number, and the
disk catalog on track $11 has a bogus
track number. (Further inspection of
track $11 confirms that there really is
a standard disk catalog on the disk.
The only problem is that third-party
disks can't see it because they trust
T11,S00 to tell them where to look
first.)

But how does the original disk know
where to look? I scoured "Beneath Apple
DOS" until I found the answer on p8-28:

                 --v--

B011-B036 Read a directory sector
; (If CARRY flag is zero on entry, read
  first directory sector. If CARRY is
  one, read next)
; Memorize entry code.
; Set buffer pointers (B045).
; First or next?
; If first, get track/sector of
  directory sector from VTOC at offset
  +1,+2.
; Otherwise, get track/sector from
  directory sector at offset +1,+2. If
  track is zero, exit with error code
  (end of directory).
; Call RWTS to read sector.
; Exit with normal return code.

                 --^--

So, to read the first sector of file
names and other metadata, this routine
is supposed to look at the VTOC sector
buffer (read from T11,S00 and stored at
$B3BB..$B4BA). The VTOC says "hey, the
first sector of files and stuff is in
T11,S0F" so this routine is supposed to
read T11,S0F.

But the DOS on this disk made one small
modification to that routine. (This is
on T01,S0F.)

B011-   08          PHP
B012-   20 45 B0    JSR   $B045
B015-   28          PLP
B016-   B0 08       BCS   $B020
B018-   AC BD B3    LDY   $B3BD
                                ------
B01B-   A2 11       LDX   #$11  << hey
B01D-   EA          NOP         << now
                                ------
B01E-   D0 0A       BNE   $B02A
B020-   AE BC B4    LDX   $B4BC
B023-   D0 02       BNE   $B027
B025-   38          SEC
B026-   60          RTS
B027-   AC BD B4    LDY   $B4BD
B02A-   8E 97 B3    STX   $B397
B02D-   8C 98 B3    STY   $B398
B030-   A9 01       LDA   #$01
B032-   20 52 B0    JSR   $B052
B035-   18          CLC
B036-   60          RTS

Instead of getting the track number
from the VTOC, it hard-codes track $11.

Now that I've identified the problem,
the fix is straightforward. If I change
the VTOC header (T11,S00) to point to
the actual first directory sector
(T11,S0F), DOS 3.3 or any other copy
utility should be able to read the
disk catalog.

T11,S00,$01 change 00 to 11

]PR#5
...
]CATALOG,S6,D1


]CATALOG,S6

C1983 DSR^C#254
052 FREE

 B 003 SSPROT$$A
 A 002 A GRAMMAR LAB
(c) 1986 MGS












 B 005 SSPROT$$1


Well that's... a little better.

                   ~

               Chapter 2
        But Wait, It Gets Worse


]LOAD A GRAMMAR LAB,S6,D1
]LIST

 5  POKE 254,128 +  ASC ("A")
 10  PRINT "BRUN SSPROT$$1"
 20  END

]BLOAD SSPROT$$1
]CALL -151

*AA72.AA73      ; address of last BLOAD

AA72- 00 08

*800L

0800-   D8          CLD
0801-   20 0B 08    JSR   $080B
0804-   4C 00 09    JMP   $0900

*80BL

; pop return address and put it in two
; seemingly arbitrary locations
080B-   68          PLA
080C-   8D 01 0B    STA   $0B01  ; =$03
080F-   68          PLA
0810-   8D 56 08    STA   $0856  ; =$08

; compute a checksum to make sure
; nothing has been modified
0813-   A9 00       LDA   #$00
0815-   AA          TAX
0816-   5D 00 08    EOR   $0800,X
0819-   CA          DEX
081A-   F0 03       BEQ   $081F
081C-   4C 16 08    JMP   $0816

; store the checksum in what was
; executable code just a few cycles ago
081F-   8D 0F 08    STA   $080F

; set the BRK vector to reboot
0822-   A9 00       LDA   #$00
0824-   8D F0 03    STA   $03F0
0827-   A9 C6       LDA   #$C6
0829-   8D F1 03    STA   $03F1

; and now a decryption loop that
; decrypts based on the checksum of the
; code
082C-   A9 08       LDA   #$08
082E-   85 B0       STA   $B0
0830-   A0 53       LDY   #$53
0832-   A9 00       LDA   #$00
0834-   85 AF       STA   $AF
0836-   85 FF       STA   $FF
0838-   A5 FF       LDA   $FF
083A-   51 AF       EOR   ($AF),Y
083C-   4D 0F 08    EOR   $080F
083F-   91 AF       STA   ($AF),Y
0841-   45 FF       EOR   $FF
0843-   85 FF       STA   $FF
0845-   EE 0F 08    INC   $080F
0848-   C8          INY
0849-   D0 ED       BNE   $0838
084B-   E6 B0       INC   $B0
084D-   A5 B0       LDA   $B0
084F-   C9 0A       CMP   #$0A
0851-   D0 E5       BNE   $0838

; everything after this is encrypted
0853-   79 2D 24    ADC   $242D,Y
0856-   61 A0       ADC   ($A0,X)
0858-   AB          ???
0859-   0A          ASL
085A-   79 58 82    ADC   $8258,Y

; note that this byte in particular was
; modified earlier based on the return
; address on the stack
0856-   61 A0       ADC   ($A0,X)

One thing at a time. This routine at
$080B is called from $0801, so the top
of the stack is going to contain $03,
then $08. $03 goes into $0B01 and $08
goes into $0856. Given that, I can
reproduce the checksum calculation
elsewhere to determine the decryption
key that ends up in $080F.

*B01:03         ; originally from stack
*856:08         ; originally from stack
*8000<800.8FFM  ; copy everything
*801E:80        ; fix in-loop JMP
*8022:60        ; stop after checksum
*8013G          ; calculate checksum
*80F            ; and the answer is...

080F- 35

*8053:60        ; stop after decryption
*802CG          ; decrypt
*853L

0853-   4C 57 08    JMP   $0857

Well, that's something.

                   ~

               Chapter 3
In Which We Get The Distinct Impression
That We're Swimming Against The Current


*857L

; execute an RWTS command with a
; custom parameter table located at
; $0874
0857-   A9 08       LDA   #$08
0859-   A0 74       LDY   #$74
085B-   20 D9 03    JSR   $03D9

*874.888

0874- 01 60 01 00
0878- 03 00 85 08 00 09 00 00
      ^^
  track $03

0880- 00 00 00 60 01 00 01 EF
0888- D8

Aha, we're seeking to the unreadable
track ($03). Probably setting up for a
nibble check on the raw data there.

; set reset vector
085E-   A9 00       LDA   #$00
0860-   8D F2 03    STA   $03F2
0863-   A9 C6       LDA   #$C6
0865-   8D F3 03    STA   $03F3
0868-   A9 63       LDA   #$63
086A-   8D F4 03    STA   $03F4

; set RUN flag (makes every command
; typed from the BASIC prompt execute
; RUN instead)
086D-   A9 80       LDA   #$80
086F-   85 D6       STA   $D6
0871-   4C 89 08    JMP   $0889

*889L

; turn on drive motor (hard-coded to
; slot 6)
0889-   A2 60       LDX   #$60
088B-   BD 8E C0    LDA   $C08E,X
088E-   BD 8C C0    LDA   $C08C,X
0891-   BD 8A C0    LDA   $C08A,X
0894-   BD 89 C0    LDA   $C089,X

A fun(*) thing to do is boot original
floppies from slot 5. Lots of copy
protection routines (including this
one) hard-code slot 6, so you can find
out when they're called because the
slot 6 drive light will suddenly go on.

(*) not guaranteed, actual fun may vary

; look for a specific nibble sequence
; "F7 F6 EF EE" while keeping some sort
; of rolling checksum
094B-   AD EC C0    LDA   $C0EC
094E-   10 FB       BPL   $094B
0950-   AA          TAX
0951-   45 FB       EOR   $FB
0953-   2A          ROL
0954-   49 41       EOR   #$41
0956-   85 FB       STA   $FB

; if Death Counter rolls over to $00
; (meaning we couldn't find this nibble
; sequence), branch to the failure path
; which turns off the drive motor and
; displays "DISK READ ERROR" message
0958-   C8          INY
0959-   F0 B3       BEQ   $090E
095B-   8A          TXA
095C-   C9 F7       CMP   #$F7
095E-   D0 EB       BNE   $094B
0960-   AD EC C0    LDA   $C0EC
0963-   10 FB       BPL   $0960
0965-   AA          TAX
0966-   45 FB       EOR   $FB
0968-   2A          ROL
0969-   49 41       EOR   #$41
096B-   85 FB       STA   $FB
096D-   C8          INY
096E-   8A          TXA
096F-   C9 F7       CMP   #$F7
0971-   F0 ED       BEQ   $0960
0973-   C9 F6       CMP   #$F6
0975-   D0 D4       BNE   $094B
0977-   AD EC C0    LDA   $C0EC
097A-   10 FB       BPL   $0977
097C-   AA          TAX
097D-   45 FB       EOR   $FB
097F-   2A          ROL
0980-   49 41       EOR   #$41
0982-   85 FB       STA   $FB
0984-   C8          INY
0985-   8A          TXA
0986-   C9 F7       CMP   #$F7
0988-   F0 D6       BEQ   $0960
098A-   C9 EF       CMP   #$EF
098C-   D0 BD       BNE   $094B
098E-   AD EC C0    LDA   $C0EC
0991-   10 FB       BPL   $098E
0993-   AA          TAX
0994-   45 FB       EOR   $FB
0996-   2A          ROL
0997-   49 41       EOR   #$41
0999-   85 FB       STA   $FB
099B-   C8          INY
099C-   8A          TXA
099D-   C9 F7       CMP   #$F7
099F-   F0 BF       BEQ   $0960
09A1-   C9 EE       CMP   #$EE
09A3-   D0 A6       BNE   $094B
09A5-   AD EC C0    LDA   $C0EC
09A8-   10 FB       BPL   $09A5
09AA-   60          RTS

Continuing from $08B6...

; compare the nibble we just read
; (at $09A5)
08B6-   C9 AB       CMP   #$AB

; if it's not $AB, jump to failure path
08B8-   D0 54       BNE   $090E

08BA-   A0 05       LDY   #$05
08BC-   20 2D 09    JSR   $092D

*92DL

; match next nibbles against an array
; of $40 known nibbles (starting at
; $09AF + an initial offset given in Y)
; over and over until we find a nibble
; that doesn't match
092D-   A2 00       LDX   #$00
092F-   AD EC C0    LDA   $C0EC
0932-   10 FB       BPL   $092F
0934-   D9 AF 09    CMP   $09AF,Y
0937-   D0 0A       BNE   $0943
0939-   C8          INY
093A-   98          TYA
093B-   29 3F       AND   #$3F
093D-   A8          TAY
093E-   D0 EF       BNE   $092F

; count the number of times we "wrapped
; around" to the start of the array
0940-   E8          INX
0941-   D0 EC       BNE   $092F

; the number of times we wrapped around
; is the number of groups of these $40
; nibbles that we found on the track
0943-   86 F9       STX   $F9

This is the nibble array (which is what
I saw on track $03 in the nibble editor
earlier -- over and over):

*9AF.9EE

09AF-                      F7
09B0- F6 EF EE AB AB AB FF D5
09B8- D5 D5 FF EA EA EA FF F5
09C0- F5 F5 FF FA FA FA FF FD
09C8- FD FD FF FE FE FE FF FF
09D0- B6 FD B7 FF BA FA BB F7
09D8- BD F7 BE BF FB BE F6 FF
09E0- FE BF BE BB EA EA FB AA
09E8- BA EA EB EF DF BF FF

Then execution continues at $0945,
which is the same entry point we called
once before, looking for "F7 F6 EF EE".

So, continuing from $08BF, after having
found "F7 F6 EF EE" for a second time:

; store Y (was used to count the total
; number of nibbles it took to find the
; sequence "F7 F6 EF EE")
08BF-   84 FA       STY   $FA

; check the last nibble read (at $09A5)
08C1-   C9 AB       CMP   #$AB

; wrong nibble --> off to The Badlands
08C3-   D0 49       BNE   $090E

; save a copy of the rolling checksum
08C5-   A5 FB       LDA   $FB
08C7-   85 FC       STA   $FC

; now start over
08C9-   A2 00       LDX   #$00
08CB-   A0 05       LDY   #$05
08CD-   AD EC C0    LDA   $C0EC
08D0-   10 FB       BPL   $08CD

; go match nibbles and count nibble
; groups again (against the array at
; $09A5, same as last time)
08D2-   20 34 09    JSR   $0934

; compare the rolling checksum we got
; this time to the one we got last time
; (but don't actually care if they
; differ -- possibly some options have
; been NOP'd out of a more generalized
; protection routine?)
08D5-   A5 FB       LDA   $FB
08D7-   C5 FC       CMP   $FC
08D9-   EA          NOP
08DA-   EA          NOP

; get number of nibble groups we found
; during $092F..0942 (stored in $F9 at
; $0943)
08DB-   A5 F9       LDA   $F9
08DD-   EA          NOP
08DE-   EA          NOP

; These were initialized to be equal
; (at $08B0). $09AC is never changed,
; so this will check whether this is
; the first time we got to this point.
08DF-   AE AD 09    LDX   $09AD
08E2-   EC AC 09    CPX   $09AC
08E5-   D0 08       BNE   $08EF

; yes, first run -- store the number of
; nibble groups we found
08E7-   8D AE 09    STA   $09AE

; loop back to do everything again
; several times
08EA-   CE AD 09    DEC   $09AD
08ED-   D0 C4       BNE   $08B3

; execution always continues here on
; anything but the first run --
; check that the total number of
; nibble groups we found is the same as
; it was last time
08EF-   4D AE 09    EOR   $09AE

; if we found a different number of
; nibble groups this time around, jump
; to The Badlands
08F2-   D0 1A       BNE   $090E

; loop back to do everything again
; several times
08F4-   CE AD 09    DEC   $09AD
08F7-   D0 BA       BNE   $08B3

; turn off drive
08F9-   AD E8 C0    LDA   $C0E8

; check the number of nibble groups we
; found each time
08FC-   AD AE 09    LDA   $09AE

; needs to be at least $5C
08FF-   C9 5C       CMP   #$5C

; otherwise -- you guessed it -- it's
; off to The Badlands
0901-   90 0B       BCC   $090E

; Success path falls through to here. I
; did not find any prior references to
; $0B00, so I believe it would be safe
; to jump straight to here to bypass
; the nibble check.
0903-   A9 60       LDA   #$60
0905-   4D 00 0B    EOR   $0B00
0908-   8D CF 03    STA   $03CF
090B-   4C FF 09    JMP   $09FF

; failure path is here -- try a few
; times before giving up completely
090E-   CE AB 09    DEC   $09AB
0911-   D0 9A       BNE   $08AD

; (giving up now) turn off drive motor,
0913-   AD E8 C0    LDA   $C0E8

; ...clear the screen,
0916-   20 58 FC    JSR   $FC58

; ...print an error message,
0919-   AD EF 09    LDA   $09EF
091C-   29 7F       AND   #$7F
091E-   AA          TAX
091F-   A0 00       LDY   #$00
0921-   B9 F0 09    LDA   $09F0,Y
0924-   20 ED FD    JSR   $FDED
0927-   C8          INY
0928-   CA          DEX
0929-   D0 F6       BNE   $0921

; ...and hang
092B-   F0 FE       BEQ   $092B

*FC58G N 400<9F0.9FEM

DISK READ ERROR

...which is exactly the behavior I saw
on my non-working copy.

                   ~

               Chapter 4
          Now It Can Be Told


It's clear that this code has been
trying to determine if the disk is
original, by counting a sequence of
nibbles on track $03. But unlike other
protection schemes, it is not at all
clear WHY this works. What makes this
particular nibble sequence so special
that it can't be copied with EDD or
some other bit copier?

To answer this question, we need to do
a very deep dive, all the way into the
bit copier code itself. Here, in the
bowels of EDD (v4.9, but versions 4.0+
share this code), is the routine that
reads nibbles from a track while
simultaneously checking if each nibble
is followed by a timing bit:

; ($00) --> $6000, the buffer to store
; raw nibbles
B834-   A9 60       LDA   #$60
B836-   85 01       STA   $01
B838-   A0 00       LDY   #$00
B83A-   84 00       STY   $00

; All nibbles need to have the high bit
; set, so EDD uses an AND mask of #$7F
; (strip the high bit) if it determines
; that a nibble is followed by a timing
; bit.
B83C-   A9 7F       LDA   #$7F
B83E-   85 02       STA   $02

; slot number (x16)
B840-   A6 10       LDX   $10

; Needless to say, this code is the
; epitome of cycle-counting, so every
; instruction matters, even if it does
; nothing but burn cycles.
B842-   EA          NOP
B843-   85 03       STA   $03
B845-   EA          NOP
B846-   EA          NOP

; The normal "LDA $C08C,X / BPL" loop
; is unrolled here. EDD tries to read
; the nibble value itself and detect
; whether a timing bit exists after it.
B847-   BD 8C C0    LDA   $C08C,X
B84A-   30 1B       BMI   $B867
B84C-   BD 8C C0    LDA   $C08C,X
B84F-   30 16       BMI   $B867

; timing bit probably present
B851-   BD 8C C0    LDA   $C08C,X
B854-   30 1B       BMI   $B871
B856-   BD 8C C0    LDA   $C08C,X
B859-   30 16       BMI   $B871
B85B-   BD 8C C0    LDA   $C08C,X
B85E-   30 11       BMI   $B871
B860-   BD 8C C0    LDA   $C08C,X
B863-   30 0C       BMI   $B871

; 3-cycle penalty if branch is taken!
; BTW, this is an unconditional branch,
; since the instruction before this
; was a BMI, and every value is either
; minus or plus.
B865-   10 E5       BPL   $B84C

; Execution continues here from $B84A
; or $B84F. No timing bit was detected,
; so store the nibble and move on.
B867-   91 00       STA   ($00),Y
B869-   C8          INY
B86A-   D0 D6       BNE   $B842
B86C-   E6 01       INC   $01
B86E-   10 D6       BPL   $B846
B870-   60          RTS

; Execution continues here from $B854,
; $B859, $B85E, or $B863. A timing bit
; was detected, so apply the AND mask
; to indicate this. (The disk write
; routine will check this later.)
B871-   25 02       AND   $02
B873-   91 00       STA   ($00),Y
B875-   C8          INY

; Unfortunately for EDD, branching here
; and applying the AND mask requires
; enough CPU cycles that we will miss
; one bit on disk by the time we branch
; back and start looking at nibble
; values again. This is usually not a
; problem, except when it is...
B876-   D0 CE       BNE   $B846
B878-   E6 01       INC   $01
B87A-   10 D0       BPL   $B84C
B87C-   60          RTS

Normally, a nibble will be shifted in
before the unrolled loop gets very far,
so execution branches to $B867 and the
nibble is stored intact. However,
because EDD only checks the data latch
six times, this nibble read routine is
vulnerable to a well-placed timing bit,
such that the "BPL" at $B865 will be
reached just before the last bit of the
nibble is shifted in. That 3-cycle time
penalty when the branch is taken is
just enough that, when combined with
the 2-cycle instruction before it, the
shift will complete, and the four CPU
cycles will elapse, before the next
read occurs.

The result is that EDD gets "out of
phase" with the proper start of the
nibbles, and the next few nibbles that
arrive will mistakenly branch to $B871
instead of $B867, losing one bit each.
When those data are written to disk by
the bit-copier, the values will be
entirely wrong.

Now imagine an entire track that is
full of repeated sequences. Each of the
sequences has a prologue, five nibbles
in length. Every other prologues has a
timing bit after each nibble. In the
middle of the track is a collection of
nibbles which do not match the
sequence, so the entire track is split
into two identical groups.

When EDD attempts to read the track, it
misses a crucial timing bit, gets "out
of phase," and ends up misreading about
half of the sequences on the track.
What remain are far fewer sequences
than exist on the original disk.

Getting back to this particular disk,
the protection check counts the total
number of nibble groups (at $08FF). It
knows that only an original disk will
have enough ($5C or more), because the
entire structure of the track exploits
this design weakness in EDD.

Fun fact(*): Copy II+'s nibble-and-
timing-bit-reading code is so similar
to EDD's that it shares the same design
weakness and is also defeated by this
protection scheme.

(*) not guaranteed, actual fun may vary

                   ~

               Chapter 5
          Just Keep Swimming,
          Just Keep Swimming


Now to continue (manually) on the
success path at $0903. Whatever needs
to end up in $03CF is probably
important later, so let's pause right
after that.

*90B:60

*903G

*9FFL

09FF-   A2 00       LDX   #$00
0A01-   BD 0F 0A    LDA   $0A0F,X
0A04-   9D 00 03    STA   $0300,X
0A07-   E8          INX
0A08-   E0 79       CPX   #$79
0A0A-   D0 F5       BNE   $0A01
0A0C-   4C 00 03    JMP   $0300

*A0C:60    ; pause again

*9FFG

*300L

; take the value that was POKEd by the
; startup program
0300-   A5 FE       LDA   $FE

; and store it later in this code
0302-   8D 76 03    STA   $0376

; don't know what this does yet
0305-   A2 68       LDX   #$68
0307-   20 5C 03    JSR   $035C

*35CL

; ah, it's executing a DOS command by
; printing a Ctrl-D followed by a
; string
035C-   A9 84       LDA   #$84
035E-   20 ED FD    JSR   $FDED
0361-   E8          INX
0362-   BD FF 02    LDA   $02FF,X
0365-   D0 F7       BNE   $035E
0367-   60          RTS

*FC58G N 400<368.378M

BLOAD SSPROT$$AM@

The last two characters are a carriage
return and a null. (They're displayed
in inverse on a real machine; sorry
that doesn't translate well to text.)
The character before that was set at
$0302 from the value POKEd by the HELLO
program.

So many layers.

This loads another file into memory by
a standard BLOAD command, so I'll
reproduce that.

*BLOAD SSPROT$$A

Then it just returns to the caller, so
let's continue the listing from there.

; oh look, another decryption loop
030A-   A9 08       LDA   #$08
030C-   85 68       STA   $68
030E-   A9 01       LDA   #$01
0310-   85 67       STA   $67
0312-   AD FF 07    LDA   $07FF
0315-   85 B0       STA   $B0
0317-   AC FE 07    LDY   $07FE
031A-   A9 00       LDA   #$00
031C-   85 AF       STA   $AF
031E-   85 FF       STA   $FF
0320-   A5 FF       LDA   $FF
0322-   51 AF       EOR   ($AF),Y
0324-   4D CF 03    EOR   $03CF
0327-   91 AF       STA   ($AF),Y
0329-   45 FF       EOR   $FF
032B-   85 FF       STA   $FF
032D-   EE CF 03    INC   $03CF
0330-   88          DEY
0331-   C0 FF       CPY   #$FF
0333-   D0 EB       BNE   $0320
0335-   C6 B0       DEC   $B0
0337-   A5 B0       LDA   $B0
0339-   C9 07       CMP   #$07
033B-   D0 E3       BNE   $0320

; now setting up a bunch of...
; Applesoft BASIC zero page globals???
033D-   AD FE 07    LDA   $07FE
0340-   85 69       STA   $69
0342-   85 6B       STA   $6B
0344-   85 6D       STA   $6D
0346-   85 AF       STA   $AF
0348-   AD FF 07    LDA   $07FF
034B-   85 6A       STA   $6A
034D-   85 6C       STA   $6C
034F-   85 6E       STA   $6E
0351-   85 B0       STA   $B0

; setting up the indirect JMP from the
; warm-start vector at $03D0
0353-   AD D2 03    LDA   $03D2
0356-   8D 5B 03    STA   $035B

; and running the BASIC program in
; memory
0359-   6C 58 9D    JMP   ($9D58)

Wait, what BASIC program in memory? The
one we just decrypted, of course. The
BLOAD SSPROT$$A command loaded an
encrypted BASIC program, then decrypted
it in place. Then this runs it, the
hard way.

It looks like the decryption loop ends
at $033D, so let's pause there and see
what's what.

*33D:60
*30AG
*3D0G

]LIST

 1  POKE 1012,0: ONERR  GOTO 6: POKE
     2049,1
 2  TEXT : HOME : IF  PEEK ( - 63
     7) = 223 THEN 4
 3  POKE  - 16368,0: PRINT
 4

 5  TEXT : HOME : HTAB 4: VTAB 10
     : FLASH : PRINT "GRAMMARLAB
     NEEDS AN APPLE IIE.": NORMAL
     : VTAB 14: PRINT "   PLEASE
     FIND THE CORRECT COMPUTER.
 6  GOTO 6
 7  POKE  - 16368,0: PRINT
 8

 ==============================
GrammarLab               HELLO

(c) Michael G. Southwell  1985
==============================


Well, would you look at that. It's a
perfectly normal BASIC program. Except,
if you look closely enough, you'll see
that line 4 is entirely blank.

]SPEED=100

]LIST 4

4  PRINT "RUNL

Then it prints backspace characters
until it overwrites that command with

4  PRI(c) 1986 MGS": REM


OK, one thing at a time... let's save
this decrypted program we just spent
so long decrypting.

]SAVE HELLO

Going back to my trusty Disk Fixer
sector editor, I see the strangely
named file that the decrypted HELLO
program seems to be running.

                 --v--

-------------- DISK EDIT --------------
TRACK $11/SECTOR $0F/VOLUME $FE/BYTE$00
---------------------------------------
$00:>00<11 0E 00 00 00 00 00   @QN@@@@@
$08: 00 00 00 06 0F 04 D3 D3   @@@FODSS
$10: D0 D2 CF D4 A4 A4 C1 A0   PROT$$A
$18: A0 A0 A0 A0 A0 A0 A0 A0
$20: A0 A0 A0 A0 A0 A0 A0 A0
$28: A0 A0 A0 A0 03 00 05 0F       C@EO
$30: 02 C1 A0 C7 D2 C1 CD CD   BA GRAMM
$38: C1 D2 A0 CC C1 C2 A0 A0   AR LAB
$40: A0 A0 A0 A0 A0 A0 A0 A0
$48: A0 A0 A0 A0 A0 A0 A0 02          B
$50: 00 12 09 82 CC 88 88 88   @RI.L...
              ^^^^^^^^^^^^^^
$58: 88 88 88 88 88 A8 E3 A9   .....(c)
     ^^^^^^^^^^^^^^^^^^^^^^^
$60: A0 B1 B9 B8 B6 A0 CD C7    1986 MG
     ^^^^^^^^^^^^^^^^^^^^^^^
$68: D3 A0 A0 A0 A0 A0 A0 A0   S
     ^^^^^^^^^^^^^^^^^^^^^^^
$70: A0 A0 05 00 12 04 82 CD     E@RD.M
$78: 88 88 88 88 88 88 88 88   ........

                 --^--

Using Copy II Plus, I can change the
boot program to this new HELLO file.

[Copy II Plus]
  [CHANGE BOOT PROGRAM]
    [SLOT 6, DRIVE 1]
      -> "HELLO"

]PR#6
...works...

The program appears to work without
setting the Applesoft zero page values,
so I'm just going to leave it like this
without recreating anything from the
encrypted startup program.

Quod erat liberandum.

                   ~

           Acknowledgements


The explanation of this copy protection
scheme was first published by qkumba in
PoC||GTFO 0x10, currently available at
https://www.alchemistowl.org/pocorgtfo/
pocorgtfo10.pdf

---------------------------------------
A 4am crack                    No. 1579
------------------EOF------------------
